// javamaps
// Copyright (c) 2010 Vasiu Cristina
package Utils.PathFinder;


import Core.*;
import Core.Link;
import Core.Street;
import java.util.*;
import java.util.ArrayList;
import Data.FileMapProvider;
import Core.IMapProvider;
import Projection.MercatorProjection;
import java.lang.Math;

public class DikstraPF implements IPathFinder{

 private ArrayList<Coordinates> path_;
 private Route route_;
 private DikstraAlgorithm dikstra_;
 private IMapProvider provider_;


 public DikstraPF(IMapProvider provider)
 {
  path_ = new ArrayList<Coordinates>();
  route_ = null;
  dikstra_ = null;
  provider_ = provider;
  dikstra_ = null;
  
 }
    
    // returneaza link-ul dintre doua noduri, null daca nu exista link;
    public Link GetLink (Node begin, Node end){
         Iterator<Link> linkIt = begin.Iterator();
           while(linkIt.hasNext()) {
               Link link = linkIt.next();
               if(link.NodeId().equals( end.Id()))
                    return link;
           }

          return null;
    }

   
    // returneaza coordonatele punctelor dintr-un link;
    public ArrayList<Coordinates> GetCoordinates (Link link, int first){

        int start, end;
        start = link.StartPoint();
        end = link.EndPoint();
        Street street = provider_.GetStreet(link.StreetId());
        ArrayList<Coordinates> coordinatesesList = new ArrayList<Coordinates>();
        List<Coordinates> coordinates = new ArrayList<Coordinates>();
        coordinates = street.Coordinates();

            for (int i = start + first ; (i <= end) && (i <= coordinates.size()) ; i ++){
               
              coordinatesesList.add(coordinates.get(i));
            }

        Collections.reverse(coordinatesesList);
        return coordinatesesList;
    }
     
   public TurnDirection Direction(Link begin, Link end)
   {
      if((begin != null) && (end != null))
      {
       Coordinates coord[] = new Coordinates[4];
      
      int startFirstLink = begin.StartPoint();
      int endFirstLink = begin.EndPoint();
      Street street1 = provider_.GetStreet(begin.StreetId());
      coord[0] = street1.Coordinates().get(startFirstLink);
      coord[1] = street1.Coordinates().get(endFirstLink);
      
      startFirstLink = end.StartPoint();
      endFirstLink = end.EndPoint();
      Street street2 = provider_.GetStreet(end.StreetId());
      coord[2] = street2.Coordinates().get(startFirstLink);
      coord[3] = street2.Coordinates().get(endFirstLink);
      

     /* if(coord[1].equals(coord[2]) == false )
         return TurnDirection.None;*/
     // pt a lucra in point
      /*
        Point point[] = new Point[4];

       MercatorProjection projection = new MercatorProjection();

      for(int i=0; i<4; i++)
          point[i] = projection.FromCoordinates(coord[i], 1);

      double a = point[1].X()-point[0].X();
       double b = point[1].Y()-point[0].Y();
       double c = point[3].X()-point[2].X();
       double d = point[3].Y()-point[2].Y();
       */

       double a = coord[1].Latitude()- coord[0].Latitude();
       double b = coord[1].Longitude()- coord[0].Longitude();
       double c = coord[3].Latitude()- coord[2].Latitude();
       double d = coord[3].Longitude()- coord[2].Longitude();
    

      // sinAlfa e sinusul ungiului dintre cele 2 link-uri
      double sinAlfa;

      sinAlfa = (Math.sqrt(Math.pow(a,2.0)+Math.pow(b,2.0))*
               Math.sqrt(Math.pow(a,2.0)+Math.pow(b,2.0)))/
               Math.sqrt(Math.pow((a*d - b*c), 2.0));

      
     if(sinAlfa == 0)
         return TurnDirection.GoAhead;
     else
         if(sinAlfa > 0)return TurnDirection.Left;
      
         else return TurnDirection.Right;
    
      }

   return TurnDirection.GoAhead;
   
   }
    public ArrayList<Coordinates> ShortestPath( ObjectId start, ObjectId stop){
        
    
      path_.clear();
       Vertex source = new Vertex(provider_.GetNode(start));
       Vertex destination = new  Vertex(provider_.GetNode(stop));
      
       dikstra_ = new DikstraAlgorithm();
       dikstra_.Dikstra(provider_, source, destination);
             
       Vertex vertex = dikstra_.hashVertex_.get(stop);
      
      if( (vertex != null) && (vertex.Previous() != null) ){
         Link link = GetLink (vertex.Previous().Node(), vertex.Node());

                path_.addAll(GetCoordinates(link, 0));
      }

       while(vertex != null)
        {
            
             if(vertex.Previous() != null)
             {
                Link link = GetLink (vertex.Previous().Node(), vertex.Node());              
                
                path_.addAll(GetCoordinates(link, 1));
               
                vertex = dikstra_.hashVertex_.get(vertex.Previous().Node().Id());
                
             }

             else 
                 vertex = null;
            
            }

            Collections.reverse(path_);
            dikstra_ = null;
            return path_;


     }

    public Route RoutePath( ObjectId start, ObjectId stop)
    {
       Vertex source = new Vertex(provider_.GetNode(start));
       Vertex destination = new  Vertex(provider_.GetNode(stop));

       dikstra_ = new DikstraAlgorithm();
       dikstra_.Dikstra(provider_, source, destination);
       ArrayList<Step> stepList = new ArrayList<Step>();
       route_ = null;
       route_ = new Route(start, stop, 0, 0, stepList);

       Vertex vertex = dikstra_.hashVertex_.get(stop);
       Link lastLink = null;

       while(vertex != null)
        {

             if(vertex.Previous() != null)
             {
                Link link = GetLink (vertex.Previous().Node(), vertex.Node());

                //path_.addAll(GetCoordinates(link, 1));

                if(lastLink != null){
                Street lastStreet = provider_.GetStreet(lastLink.StreetId());
                Street street = provider_.GetStreet(link.StreetId());

                //se verifica dc s-a trecut la o alta strada daca da se genereaza
                //un nou step altfel se acutalizeaza nodul de stop
                if(lastStreet.Id().equals(street.Id()))
                {
                    Step step = new Step();
                    step = route_.LastStep();

                    if(route_.StepList() != null )

                        route_.DeleteStep(step);
                        step.SetStart(vertex.Previous().Node().Id());
                        step.SetDistance(step.Distance() + link.Distance());
                        route_.AddStep(step);
                        route_.SetDistance(route_.Distance() + link.Distance());

                }
                else{

                    Step step = new Step(vertex.Previous().Node().Id(),
                            vertex.Node().Id(), link.Distance(),
                            link.StreetId(), Direction(link, lastLink));

                            route_.AddStep(step);
                            route_.SetDistance(route_.Distance() + link.Distance());
                            route_.SetCountStep(route_.CountStep() + 1);
                }
                }
                else{
                Step step = new Step(vertex.Previous().Node().Id(), vertex.Node().Id(), link.Distance(),
                                    link.StreetId(), Direction(link, lastLink));

                 route_.AddStep(step);
                 route_.SetCountStep(route_.CountStep() + 1);
                 route_.SetDistance(route_.Distance() + link.Distance());
                }
                lastLink = link;
                vertex = dikstra_.hashVertex_.get(vertex.Previous().Node().Id());

             }

             else
                 vertex = null;


            }

            stepList = route_.StepList();
            Collections.reverse(stepList);
            route_.SetStepList(stepList);

            //Collections.reverse(path_);
            dikstra_ = null;

        return route_;
    }
    public ArrayList<Coordinates> Path(){ return path_; }
    public void SetPath(ArrayList<Coordinates> path){path_ = path;}

    public void AddCostProvider(ICostProvider cost){}
    public void RemoveCostProvider(ICostProvider cost){}
    public void ClearCostProviders(){}

}